
namespace hms::vtk
{

namespace internal {

template<typename T>
xml::XMLError readAttrVal(
	const xml::XMLElement* elem,
	const char* attribute,
	T&& val
){
	xml::XMLError e;
	if constexpr ( std::is_same_v<remove_qualifiers<T>, const char*> ){
		e = elem->QueryStringAttribute(attribute, &val);
	} else if constexpr ( std::is_fundamental_v<remove_qualifiers<T>> ){
		e = elem->QueryAttribute(attribute, &val);
	} else {
		assert(false);
	}
	return e;
}

} // end namespace internal

template<typename T>
std::string lineNum( const T* t ){
	int ln { t->GetLineNum() };
	if ( ln != 0 )
		return "Line " + std::to_string(ln);
	else
		return "unknown line";
}

template<typename T>
T getAttributeValueWithDefault(
	const xml::XMLElement* elem,
	const char* attribute,
	T defaultVal
){
	xml::XMLError e { internal::readAttrVal(elem, attribute, defaultVal) };

	if ( e != xml::XML_SUCCESS ){
		std::stringstream conv;
		conv << std::boolalpha << defaultVal;
		if ( conv.fail() )
			assert(false && "Error message cannot be properly constructed!");

		log::getLogger()->warn(
			internal::readAttrError(elem, attribute)
			+ "\nUsing default value \"{}\".", bold( conv.str() )
		);
	}

	return defaultVal;
}

template<typename T>
T getAttributeValue(
	const xml::XMLElement* elem,
	const char* attribute
){
	T val;
	xml::XMLError e { internal::readAttrVal(elem, attribute, val) };

	if ( e != xml::XML_SUCCESS )
		throw ParseError( internal::readAttrError(elem, attribute) );
	
	return val; 
}


template<typename T>
maybe_const_XMLElement<T>* getChildNodeByName(
	T* parent,
	const char* name
){
	static_assert( std::is_base_of_v<xml::XMLNode, std::remove_const_t<T>>	);

	maybe_const_XMLElement<T>* child { parent->FirstChildElement(name) };
	if (child)
		return child;
	else
		throw ParseError(
			"Child node \""s + name + "\" not found in\n" + nodeString(parent)
		);
}

namespace internal {

template<typename T, typename A>
maybe_const_XMLElement<T>* getChildNodeByAttributeValue(
	T* parent,
	const char* name,
	const char* attribute,
	A&& targetValue
){
	maybe_const_XMLElement<T>* child { parent->FirstChildElement(name) };

	while (child){
		if constexpr ( std::is_convertible_v<A, std::string_view> ){
			const char* val { child->Attribute(attribute) };
			if ( std::string_view{val} == std::string_view{targetValue} )
				return child;
		} else if constexpr ( std::is_fundamental_v<remove_qualifiers<A>> ){
			remove_qualifiers<A> val;
			xml::XMLError e { child->QueryAttribute(attribute, &val) };
			if ( e == xml::XML_SUCCESS && val == targetValue )
				return child;
		} else {
			throw std::logic_error(
				"Cannot convert xml attribute values to complex data types!"
			);
		}

		child = child->NextSiblingElement(name);
	}
	return nullptr;
}

} // namespace internal


template<typename T, typename A>
maybe_const_XMLElement<T>* getChildNodeByAttributeValue(
	T* parent,
	const char* name,
	const char* attribute,
	A&& targetValue
){
	maybe_const_XMLElement<T>* child { internal::getChildNodeByAttributeValue(
		parent, name, attribute, std::forward<A>(targetValue)
	)};

	if (child)
		return child;

	auto targetToString = [&]() -> std::string {
		if constexpr ( std::is_fundamental_v<remove_qualifiers<A>> ){
			return std::to_string(targetValue);
		} else if constexpr ( std::is_convertible_v<A, std::string> ){
			return {targetValue};
		}
	};

	throw ParseError(
		"Could not find child \""s + name +
		"\" with attribute " + attribute + "=\"" + targetToString() +
		"\" in\n" + nodeString(parent)
	);
}


template<typename T>
maybe_const_XMLElement<T>*
	getVtkNode( T* doc )
{
	static_assert( std::is_same_v<xml::XMLDocument, remove_qualifiers<T>> );
	return getChildNodeByName(doc, rootName);
}


template<typename MeshType, typename T>
maybe_const_XMLElement<T>* getMeshNode( T* vtkNode ){

	constexpr auto* type { getVtkFormat<MeshType>() };

	if ( !vtkNode->Attribute("type", type) )
		throw hms::ParseError(
			"VTK type is not \""s + type + "\"! " +
			"Check file extension!"
		);

	return getChildNodeByName( vtkNode, type );
}


template<typename MeshType, typename T>
maybe_const_XMLElement<T>* getCellData( T* vtkNode ){
	return getChildNodeByName(
		getChildNodeByName(getMeshNode<MeshType>(vtkNode), "Piece"),
		"CellData"
	);
}


template<typename T>
maybe_const_XMLElement<T>* getBoundaryNode( T* parent ){
	return getChildNodeByName(parent, boundaryNode);
}


template<typename T>
maybe_const_XMLElement<T>* getBoundaryPatch(
	T* boundary, const std::string& patchName
){
	return getChildNodeByAttributeValue(boundary, boundaryChild, "Name", patchName);
}


template<typename T>
maybe_const_XMLElement<T>* getBoundaryPatch(
	T* patches, const BoundaryPatch& patch
){
	return getBoundaryPatch( patches, patch.name() );
}


} // namespace hms::vtk
